package com.ruoyi.business.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.ruoyi.bizsys.domain.*;
import com.ruoyi.bizsys.enums.EnumFeeType;
import com.ruoyi.bizsys.service.IAccountFlowService;
import com.ruoyi.bizsys.service.ICustBankCardService;
import com.ruoyi.bizsys.service.ICustBindCardLogService;
import com.ruoyi.bizsys.service.ILoanOverflowService;
import com.ruoyi.bizsys.utils.BizConstants;
import com.ruoyi.business.common.AsyncService;
import com.ruoyi.business.domain.BankVO;
import com.ruoyi.business.domain.WalletAccountReq;
import com.ruoyi.business.service.IBankCardService;
import com.ruoyi.business.service.IBankListService;
import com.ruoyi.business.service.IRaiseLimitService;
import com.ruoyi.business.service.IWalletService;
import com.ruoyi.business.task.thread.BindBankCardThread;
import com.ruoyi.common.constant.WalletConstants;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.enums.RspEnmu;
import com.ruoyi.common.exception.BusinessException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.Func;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.business.CheckAccountNameUtil;
import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.extdata.common.config.PayStackConfig;
import com.ruoyi.extdata.service.IExternalApiService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

/**
 * @Author: Jamnsin
 * @Date: 2021/3/24 10:32 上午
 */
@Slf4j
@Service
public class BankCardSerivceImpl extends BaseService implements IBankCardService {

    @Autowired
    IExternalApiService externalApiService;

    @Autowired
    IBankListService bankListService;

    @Autowired
    PayStackConfig payStackConfig;

    @Autowired
    ICustBindCardLogService custBindCardLogService;

    @Autowired
    ICustBankCardService custBankCardService;

    @Autowired
    RedisCache redisCache;

    @Autowired
    ILoanOverflowService loanOverflowService;

    @Autowired
    IRaiseLimitService raiseLimitService;

    @Autowired
    AsyncService asyncService;

    @Autowired
    IAccountFlowService accountFlowService;

    @Autowired
    IWalletService walletService;

    private static final String EMAIL = "@LCserver.com";
    private static final String TRANS_AMT = "5000";

    @Override
    public List<BankVO> getPayStackCode() {
        List<BankVO> bankVOs = new ArrayList<>();
        List<BankCode> bankLists = bankListService.getBankListSorted();
        bankLists.stream().forEach(p -> {
                    if (!Func.hasEmpty(p.getPaystackBankCode(), p.getPaystackBankName())) {
                        bankVOs.add(BankVO.builder().bankCode(p.getPaystackBankCode()).bankName(p.getPaystackBankName()).build());
                    }
                }
        );
        return bankVOs;
    }

    @Override
    public JSONObject getBindAuthUrl() {

        String userId = getUserId();
        String userEmail = userId.substring(userId.length() - 10) + EMAIL;
        String bindSerialNo = IdUtils.reqId() + BizConstants.PAY_TRANS_FLAG_BIND_CARD;

        CustBindCardLog custBindCardLog = new CustBindCardLog();
        custBindCardLog.setUserId(userId);
        custBindCardLog.setCustNo(getCustNo(userId));
        custBindCardLog.setBindSerialNo(bindSerialNo);
        custBindCardLog.setEmail(userEmail);
        custBindCardLog.setTransAmt(new BigDecimal(TRANS_AMT));
        custBindCardLog.setTransStatus(BizConstants.BIND_CARD_GET_AUTH_URL);
        custBindCardLogService.insertCustBindCardLog(custBindCardLog);

        JSONObject reqParam = new JSONObject();
        reqParam.put("email", userEmail);
        reqParam.put("amount", TRANS_AMT);
        reqParam.put("reference", bindSerialNo);
        // reqParam.put("callback_url", "http://jamnsinx.antiantech.com/callback/bind/res");
        // {"status":true,"message":"Authorization URL created","data":{"authorization_url":"https://checkout.paystack.com/0peioxfhpn","access_code":"0peioxfhpn","reference":"7PVGX8MEk85tgeEpVDtD"}}
        JSONObject apiRes = null;
        try {
            apiRes = externalApiService.invokePayStackApi("Initialize-Transaction", payStackConfig.getInitializeTransaction(), "POST", reqParam, null);
        } catch (BusinessException e) {
            custBindCardLog.setRemark(e.getDetails());
            custBindCardLog.setTransStatus(BizConstants.BIND_CARD_GET_AUTH_URL_ERR);
            custBindCardLogService.updateCustBindCardLog(custBindCardLog);
            throw e;
        }

        CustBindCardLog custBindLog = new CustBindCardLog();
        custBindLog.setId(custBindCardLog.getId());
        custBindLog.setAccessCode(apiRes.getString("access_code"));
        custBindLog.setAuthorizationUrl(apiRes.getString("authorization_url"));
        custBindLog.setTransStatus(BizConstants.BIND_CARD_WAIT_CUST_SUBMIT);
        custBindCardLogService.updateCustBindCardLog(custBindLog);

        JSONObject rst = new JSONObject();
        rst.put("authorizationUrl", custBindLog.getAuthorizationUrl());
        rst.put("bindSerialNo", bindSerialNo);

        return rst;
    }

    @Override
    public JSONObject verifyTransaction(CustBindCardLog bindLog) {
        JSONObject apiRes = null;

        CustBindCardLog cardLog = new CustBindCardLog();
        cardLog.setId(bindLog.getId());

        try {
            apiRes = externalApiService.invokePayStackApi("Verify-Transaction-Bind", payStackConfig.getVerifyTransaction().replace("REFERENCE", bindLog.getBindSerialNo()), "GET", null, null);
            externalApiService.dealTransferData("bind." + apiRes.getString("status"), "interface", bindLog.getCustNo(), apiRes);
        } catch (BusinessException e) {
            log.error("Verify-Transaction-Bind 验证绑卡交易异常：{}, {}", e.getCode(), e.getMessage());
            if (RspEnmu.EXTERNAL_API_RSP_FALSE.getCode().equals(e.getCode())) {
                cardLog.setRemark(e.getDetails());
                cardLog.setTransStatus(BizConstants.BIND_CARD_TRANS_FAILED);
                custBindCardLogService.updateCustBindCardLog(cardLog);
            }
            return apiRes;
        }

        String transStatus = apiRes.getString("status");
        String gatewayResponse = apiRes.getString("gateway_response");
        cardLog.setRemark(gatewayResponse);

        if (!StringUtils.equals(transStatus, "success")) {
            switch (transStatus) {
                case "ongoing":
                case "pending":
                    if (StringUtils.equals(bindLog.getTransStatus(), BizConstants.BIND_CARD_TRANS_PENDING)) {
                        return apiRes;
                    }
                    cardLog.setTransStatus(BizConstants.BIND_CARD_TRANS_PENDING);
                    break;
                case "failed":
                case "abandoned":
                    double interval = DateUtils.getDateDiff(7, new Date(), bindLog.getCreateTime());
                    if (interval <= 600) {
                        if (StringUtils.equals(bindLog.getTransStatus(), BizConstants.BIND_CARD_TRANS_FAILED)) {
                            cardLog.setTransStatus(BizConstants.BIND_CARD_TRANS_PENDING);
                        }
                    } else {
                        cardLog.setTransStatus(BizConstants.BIND_CARD_TRANS_FAILED);
                    }
                    break;
                case "timeout":
                    if (StringUtils.equals(bindLog.getTransStatus(), BizConstants.BIND_CARD_TRANS_TIMEOUT)) {
                        return apiRes;
                    }
                    cardLog.setTransStatus(BizConstants.BIND_CARD_TRANS_TIMEOUT);
                    break;
            }
            custBindCardLogService.updateCustBindCardLog(cardLog);
            return apiRes;
        }

        if (apiRes.getJSONObject("authorization").size() == 0) {
            cardLog.setTransStatus(BizConstants.BIND_CARD_WAIT_CUST_SUBMIT);
            custBindCardLogService.updateCustBindCardLog(cardLog);
            return apiRes;
        }

        dealBindSuccess(bindLog, apiRes);
        return apiRes;
    }

    @Override
    public void dealBindSuccess(CustBindCardLog bindLog, JSONObject apiRes) {
        String gatewayResponse = apiRes.getString("gateway_response");
        JSONObject authorization = apiRes.getJSONObject("authorization");

        JSONObject customer = apiRes.getJSONObject("customer");
        String cardNumber = authorization.getString("bin") + authorization.getString("last4");
        String expireDate = authorization.getString("exp_year") + authorization.getString("exp_month");
        String authorizationCode = authorization.get("authorization_code") == null ? "" : authorization.getString("authorization_code");
        double interval = DateUtils.getDateDiff(DateUtils.DATE_INTERVAL_DAY, DateUtils.dateTime(DateUtils.YYYYMM, expireDate), DateUtils.dateTime(DateUtils.YYYYMM, DateUtils.dateTimeNow(DateUtils.YYYYMM)));
        boolean reusable = authorization.getBoolean("reusable");

        String bankName = authorization.get("bank") == null ? "" : authorization.getString("bank").trim();
        String bankCode = getBankCode(bankName);

        CustBindCardLog custBindCardLog = new CustBindCardLog();
        custBindCardLog.setId(bindLog.getId());
        custBindCardLog.setCustNo(bindLog.getCustNo());
        custBindCardLog.setAuthorizationCode(authorizationCode);
        custBindCardLog.setCardNumber(cardNumber);
        custBindCardLog.setCardType(authorization.getString("card_type"));
        custBindCardLog.setBankCode(bankCode);
        custBindCardLog.setBankName(bankName);
        custBindCardLog.setExpireDate(expireDate);
        custBindCardLog.setCountry(authorization.getString("country_code"));
        custBindCardLog.setCurrency(authorization.getString("currency"));
        custBindCardLog.setTransactionDate(authorization.getString("transaction_date"));
        custBindCardLog.setCustomerCode(customer.getString("customer_code"));
        custBindCardLog.setPhoneNumber(customer.getString("phone"));
        custBindCardLog.setRemark(gatewayResponse);
        custBindCardLog.setAccountName(authorization.getString("account_name"));

        //卡片状态
        String bindStatus = BizConstants.BIND_CARD_TRANS_SUCCESS;
        //卡片有效期状态
        String cardStatus = "1";
        if (StringUtils.isBlank(authorizationCode) || !(reusable && (interval / 30 >= 3))) {
            log.info("不满足绑卡三要素， authorizationCode = {},reusable = {},interval = {}", authorizationCode, reusable, interval);
            cardStatus = "0";
        }
        custBindCardLog.setTransStatus(bindStatus);


        // 2021-12-2 修复绑卡三要素校验不通过 bug，2021-12-08 按 legolas 邮件要求恢复此 bug
//        if(StringUtils.equals(bindStatus, BizConstants.BIND_CARD_TRANS_FAILED)) {
//            return;
//        }

        CustBindCardLog cardLog = new CustBindCardLog();
        cardLog.setId(bindLog.getId());

        CustBankCard bankCard = new CustBankCard();
        bankCard.setCardNumber(cardNumber);
        bankCard.setState(BizConstants.BIZ_STATUS_1);
        List<CustBankCard> custBankCardList = custBankCardService.selectCustBankCardList(bankCard);
        if (!CollectionUtils.isEmpty(custBankCardList)) {

            for (int i = 0; i < custBankCardList.size(); i++) {
                CustBankCard custBankCard = custBankCardList.get(i);

                //接口返回姓名是否为空
                String oldName = getName(custBindCardLog.getAccountName());
                String newName = getName(custBankCard.getAccountName());

                if (StringUtils.isNotBlank(newName) && StringUtils.isNotBlank(oldName) && oldName.split(" ").length >= 2 && newName.split(" ").length >= 2) {
                    if (CheckAccountNameUtil.verifyName(oldName, newName)) {
                        //相同失败
                        log.info("姓名相同失败,bindId:{},custBankCardId:{}", bindLog.getId(), custBankCard.getId());

                        updateCustBindCardLog(custBankCard, bindLog, cardLog, cardNumber, expireDate);
                        return;
                    }
                } else {
                    if (custBankCard.getExpireDate().equals(custBindCardLog.getExpireDate())) {
                        log.info("有效期相同失败,bindId:{},custBankCardId:{}", bindLog.getId(), custBankCard.getId());

                        updateCustBindCardLog(custBankCard, bindLog, cardLog, cardNumber, expireDate);
                        return;
                    }
                }
            }

        }
        custBindCardLogService.updateCustBindCardLog(custBindCardLog);

        CustBindCardLog custBindLog = custBindCardLogService.selectCustBindCardLogById(bindLog.getId());
        CustBankCard custBankCard = new CustBankCard();
        BeanUtils.copyProperties(custBindLog, custBankCard);
        custBankCard.setRemark(gatewayResponse);
        custBankCard.setCardStatus(cardStatus);

        CustBankCard custCard = new CustBankCard();
        custCard.setUserId(bindLog.getUserId());
        custCard.setState(BizConstants.BIZ_STATUS_1);
        custBankCardList.clear();
        custBankCardList = custBankCardService.selectCustBankCardList(custCard);
        if (CollectionUtils.isEmpty(custBankCardList)) {
            custBankCard.setIsDefault("Y");
        }

        custBankCardService.insertCustBankCard(custBankCard);

        //记录绑卡流水
        accountWater(bindLog, "1");

        //修改用户标签
        asyncService.updateAppUserMark(bindLog.getUserId(), "custBankCard");

        raiseLimitService.bindCardRaiseLimit(custBindLog.getCustNo());
        return;
    }

    /**
     * 账目流水
     *
     * @param bindLog
     * @param type
     */
    private void accountWater(CustBindCardLog bindLog, String type) {
        AccountFlow accountFlow = new AccountFlow();
        accountFlow.setCustNo(bindLog.getCustNo());
        if (type.equals("1")) {
            accountFlow.setFeeType(EnumFeeType.BIND_FEE_IN.getCode());
        } else {
            accountFlow.setFeeType(EnumFeeType.OVERPAY_OUT.getCode());
        }
        accountFlow.setAmt(bindLog.getTransAmt().divide(new BigDecimal(100)).setScale(4, BigDecimal.ROUND_DOWN));
        accountFlow.setRelativeSerialNo(bindLog.getBindSerialNo());
        accountFlow.setTransferChannel(BizConstants.OVERFLOW_TYPE_REPEAT_BINDBANK);
        accountFlow.setTransferTime(new Date());
        accountFlow.setDataSource("cust_bink_card");
        accountFlowService.insertByIndex(accountFlow);
    }


    public void updateCustBindCardLog(CustBankCard custBankCard, CustBindCardLog bindLog, CustBindCardLog cardLog, String cardNumber, String expireDate) {
        if (StringUtils.equals(custBankCard.getUserId(), bindLog.getUserId())) {
            if (!StringUtils.equals(custBankCard.getBindSerialNo(), bindLog.getBindSerialNo())) {
                log.info("用户 {} 已绑定过此银行卡：{}，有效期：{}", custBankCard.getUserId(), cardNumber, expireDate);
                cardLog.setTransStatus(BizConstants.BIND_CARD_REPEAT_OPERATION);
            }
        } else {
            log.info("当前用户：{} 提交的银行卡：{}，有效期：{}，已被其他用户 {} 绑定", bindLog.getUserId(), cardNumber, expireDate, custBankCard.getUserId());
            cardLog.setTransStatus(BizConstants.BIND_CARD_BY_OHTERS);
        }

        cardLog.setTransAmt(bindLog.getTransAmt().divide(new BigDecimal(100)).setScale(4, BigDecimal.ROUND_DOWN));
        custBindCardLogService.updateCustBindCardLog(cardLog);

        bindLog.setTransAmt(bindLog.getTransAmt().divide(new BigDecimal(100)).setScale(4, BigDecimal.ROUND_DOWN));
        dealOverflow(bindLog);
    }

    public void dealOverflow(CustBindCardLog bindLog) {
        //锁住客户账户再操作
        String lockKey = RedisCache.BANK_CARD_LOCK_KEY_PREFIX + bindLog.getBindSerialNo();
        RLock lock = redisCache.lock(lockKey);
        try {
            //防止并发，再查询一下用户的
            List<LoanOverflow> loanOverflows = getLoanOverflows(bindLog);
            if (loanOverflows.size() != 0) {
                return;
            }
            //添加溢缴款记录
            addOverflowIncomeAndExpense(bindLog);

            // 更新钱包
            walletService.updateWalletAccount(getWalletAccountReq(bindLog));

            //修改用户溢缴金额
            //  creditAccountService.updateOverflowAmount(bindLog.getCustNo(), bindLog.getTransAmt().divide(new BigDecimal(100)).setScale(4, BigDecimal.ROUND_DOWN));
            //记录账户流水
            accountWater(bindLog, "2");

        } catch (Exception e) {
            log.error("BindCard 线程 sleep 异常", e);
        } finally {
            lock.unlock();
        }
    }

    private WalletAccountReq getWalletAccountReq(CustBindCardLog bindLog) {
        WalletAccountReq walletAccountReq = WalletAccountReq.builder()
                .amount(bindLog.getTransAmt())
                .custNo(bindLog.getCustNo())
                .reference(bindLog.getBindSerialNo())
                .fundsType(WalletConstants.WALLET_FLOW_FUNDS_TYPE_BIND_CARD)
                .build();
        return walletAccountReq;
    }

    private void addOverflowIncomeAndExpense(CustBindCardLog bindLog) {
        saveOverflowIncome(bindLog);
        saveOverflowExpense(bindLog);
    }

    private void saveOverflowExpense(CustBindCardLog bindLog) {
        LoanOverflow loanOverflow = new LoanOverflow();
        loanOverflow.setChangeAmount(bindLog.getTransAmt().negate());
        loanOverflow.setCustNo(bindLog.getCustNo());
        loanOverflow.setChangeType(BizConstants.OVERFLOW_CHANGE_TYPE_INCOME);
        loanOverflow.setType(WalletConstants.OVERFLOW_TO_WALLET);
        loanOverflow.setTransactionReference(bindLog.getBindSerialNo());
        loanOverflowService.insertLoanOverflow(loanOverflow);
    }

    private void saveOverflowIncome(CustBindCardLog bindLog) {
        LoanOverflow loanOverflow = new LoanOverflow();
        loanOverflow.setChangeAmount(bindLog.getTransAmt());
        loanOverflow.setCustNo(bindLog.getCustNo());
        loanOverflow.setChangeType(BizConstants.OVERFLOW_CHANGE_TYPE_INCOME);
        loanOverflow.setType(BizConstants.OVERFLOW_TYPE_REPEAT_BINDBANK);
        loanOverflow.setTransactionReference(bindLog.getBindSerialNo());
        loanOverflow.setOverflowTime(new Date());
        loanOverflowService.insertLoanOverflow(loanOverflow);
    }

    private List<LoanOverflow> getLoanOverflows(CustBindCardLog bindLog) {
        LoanOverflow loanOverflow1 = new LoanOverflow();
        loanOverflow1.setTransactionReference(bindLog.getBindSerialNo());
        loanOverflow1.setChangeType(BizConstants.OVERFLOW_CHANGE_TYPE_INCOME);
        loanOverflow1.setType(BizConstants.OVERFLOW_TYPE_REPEAT_BINDBANK);
        List<LoanOverflow> loanOverflows = loanOverflowService.selectLoanOverflowList(loanOverflow1);
        return loanOverflows;
    }

    public static String getName(String name) {
        if (StringUtils.isNotBlank(name)) {
            String[] s = name.split(" ");
            String str = "";
            for (int i = 0; i < s.length; i++) {
                if (StringUtils.isNotBlank(s[i])) {
                    str += " " + s[i];
                }
            }
            return str.trim().toUpperCase();
        }
        return "";
    }

    @Override
    public String getBankCode(String bankName) {
        if (StringUtils.isBlank(bankName)) {
            return "";
        }
        String bankCode = "";
        BankCode bankList = new BankCode();
        bankList.setPaystackBankName(bankName);
        bankList.setState(BizConstants.BIZ_STATUS_1);
        BankCode bankCodeE = bankListService.getBankCode(bankList);
        if (!Func.isEmpty(bankCodeE)) {
            bankCode = bankCodeE.getPaystackBankCode();
        }
        return bankCode;
    }

    @Override
    public void startPolling(CustBindCardLog custBindCardLog) {
        //判断当前是否正在轮询
        String cacheObject = redisCache.getCacheObject("bind-bank-card-thread:" + custBindCardLog.getBindSerialNo());
        if (cacheObject != null && !StringUtils.isBlank(cacheObject)) {
            return;
        }

        //判断当前时间
        Date startOfDate = Calendar.getInstance().getTime();
        Calendar calendar2 = Calendar.getInstance();
        calendar2.set(calendar2.get(Calendar.YEAR), calendar2.get(Calendar.MONTH), calendar2.get(Calendar.DAY_OF_MONTH),
                23, 00, 00);
        Date endOfDate = calendar2.getTime();

        //如果当前时间小于23点之前
        if (startOfDate.before(endOfDate)) {
            BindBankCardThread bindBankCard = new BindBankCardThread(custBindCardLog, redisCache, this, custBindCardLogService);
            bindBankCard.start();
        }
    }

    @Override
    public List<CustBindCardLog> getBindRecords() {

        return null;
    }

    @Override
    public void cardStrategyCheck() {
        CustomerInfo customerInfo = getCustomerInfo();
        BigDecimal loanQuotaRatio = getLoanQuotaRatio(customerInfo);
        if (loanQuotaRatio.compareTo(BigDecimal.ZERO) == 0) {
            throw new BusinessException(RspEnmu.PRE_LOAN_BANK_ERR);
        }
//
//        log.info("卡策略:{}", customerInfo.getCustNo());
//        //有效绑定卡数量>=风控绑卡标识，未绑卡提现额度比例为100%，服务器计算并判断用户当前可提现额度（剩余授信金额*未绑卡提现额度比例）是否大于等于2000。
//        int cardBound = custNeedBindCardNum(customerInfo);
//        //需要绑卡的数量 <= 0,未绑卡提现额度比例为100%，
//        if (cardBound <= 0) {
//            CreditAccount creditAccount = creditAccountService.getByCustNo(customerInfo.getCustNo());
//            //冻结额度
//            BigDecimal frozenQuota = Optional.ofNullable(creditAccount.getFrozenQuota()).orElse(BigDecimal.ZERO);
//            //在贷额度 + 冻结额度 = 剩余可借额度
//            BigDecimal borrowedQuota = Optional.ofNullable(creditAccount.getLoanQuota()).orElse(BigDecimal.ZERO).add(frozenQuota);
//            //授信额度 - 借款额度 = 剩余可借额度
//            BigDecimal canQuota = creditAccount.getCreditLine().subtract(borrowedQuota);
//            //是否大于等于2000。
//            int i = canQuota.compareTo(new BigDecimal(2000));
//            if (i >= 0) return;
//        }


    }

    /**
     * 客户需要绑卡数量
     * 风控绑卡标识 - 有效绑定卡数量
     *
     * @param customerInfo
     * @return
     */
    @Override
    public int custNeedBindCardNum(CustomerInfo customerInfo) {
        //用户绑卡数
        int cardBound = custBankCardService.customerCardAndTransCount(customerInfo.getCustNo());
        //最大绑卡标识
        int needBindCardNum = custBankCardService.getCardMaxLimit(customerInfo);
        return needBindCardNum - cardBound;
    }

    @Override
    public BigDecimal getLoanQuotaRatio(CustomerInfo customerInfo) {
        int needBindCardNum = customerInfo.getNeedBindCardNum();
        if (needBindCardNum > 0) {
            //>0 ,已绑卡数量是否大于0 ，是 100
            int cardBound = custBankCardService.customerCardAndTransCount(customerInfo.getCustNo());
            if (cardBound > 0) {
                return BigDecimal.ONE;
            }
        }
        return BigDecimal.ZERO;
    }

}
